home *** CD-ROM | disk | FTP | other *** search
/ Aminet 1 (Walnut Creek) / Aminet - June 1993 [Walnut Creek].iso / usenet / sources / volume2 / unix / dcron.1 next >
Text File  |  1988-12-28  |  38KB  |  1,075 lines

  1. Path: xanth!nic.MR.NET!csd4.milw.wisc.edu!mailrus!cornell!rochester!bbn!ulowell!page
  2. From: page@swan.ulowell.edu (Bob Page)
  3. Newsgroups: comp.sources.amiga
  4. Subject: v02i108:  dcron - cron utility
  5. Message-ID: <10953@swan.ulowell.edu>
  6. Date: 28 Dec 88 21:42:23 GMT
  7. Organization: University of Lowell, Computer Science Dept.
  8. Lines: 1064
  9. Approved: page@swan.ulowell.edu
  10.  
  11. Submitted-by: dillon@postgres.berkeley.edu (Matt Dillon)
  12. Posting-number: Volume 2, Issue 108
  13. Archive-name: unix/dcron.1
  14.  
  15. [uuencoded executable included.  ..Bob]
  16.  
  17. #    This is a shell archive.
  18. #    Remove everything above and including the cut line.
  19. #    Then run the rest of the file through sh.
  20. #----cut here-----cut here-----cut here-----cut here----#
  21. #!/bin/sh
  22. # shar:    Shell Archiver
  23. #    Run the following text with /bin/sh to create:
  24. #    dcron.c
  25. #    dcron.doc
  26. #    Mountlist
  27. #    dcron.uu
  28. #    null-handler.uu
  29. # This archive created: Wed Dec 28 16:36:08 1988
  30. cat << \SHAR_EOF > dcron.c
  31.  
  32. /*
  33.  *  DCRON.C  V1.05
  34.  *
  35.  *  Limitations:    The only RUN commands allowed are of the form:
  36.  *
  37.  *            RUN >nil: <nil: <command>
  38.  *
  39.  *  -Wakeup once every minute & check the modify date for S:CRONTAB
  40.  *   and to check if the DateStamp() has been re-set.
  41.  *
  42.  *  -Attempt to be smart about checking s:crontab (scan the file
  43.  *   as little as possible).
  44.  *
  45.  *  -Handles massive Date changes and crontab modifications
  46.  *
  47.  *  DCRON [-d] logfile
  48.  */
  49.  
  50. #include <stdio.h>
  51. #include <local/typedefs.h>
  52.  
  53. #define CRONTAB "s:crontab"
  54. #define SIGS    (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D|SIGBREAKF_CTRL_E|SIGBREAKF_CTRL_F)
  55.  
  56. #define ACTION_READWRITE    1004
  57. #define ACTION_FIND_INPUT   1005        /* various ACTION's supported   */
  58. #define ACTION_FIND_OUTPUT  1006
  59. #define ACTION_END        1007
  60. #define ACTION_EXAMINE        23
  61. #define ACTION_EXAMINENEXT  24
  62. #define ACTION_LOCATE        8
  63. #define ACTION_FREELOCK     15
  64. #define ACTION_COPYDIR        19
  65.  
  66. #define DOS_FALSE    0
  67. #define DOS_TRUE     -1
  68.  
  69. #define BTOC(bptr)  ((void *)((long)(bptr) << 2))
  70.  
  71. typedef struct FileHandle FH;
  72.  
  73. typedef struct {
  74.     short min;
  75.     short hour;
  76.     short day;
  77.     short month;
  78.     short dow;
  79. } MHDMW;
  80.  
  81. typedef struct {
  82.     short entry[5];
  83. } MHDMWARY;
  84.  
  85. typedef struct {
  86.     MHDMW   Beg;
  87.     MHDMW   End;
  88. } CDATE;
  89.  
  90. typedef struct {
  91.     char min[60];
  92.     char hour[24];
  93.     char day[32];
  94.     char month[13];
  95.     char dow[7];
  96. } CARRY;
  97.  
  98. extern    int    Enable_Abort;
  99. extern    APTR    DeviceProc();
  100.  
  101. DATESTAMP   BegDate;        /*    Start and end range for     */
  102. DATESTAMP   EndDate;        /*     file compare            */
  103. DATESTAMP   ModDate;        /*    Check if date modified        */
  104. DATESTAMP   TabDate;        /*    Check if crontab modified   */
  105.  
  106. IOT    Iot;            /*    Best guess at next timeout        */
  107. IOT    Iotmin;         /*    1 minute T.O. (date/file modified test  */
  108. PORT    *TPort;         /*    Collector plate for IO requests     */
  109. short    FatalError;        /*    Uh oh, can't recover                    */
  110. short    NextTimeout;        /*    # minutes till next timeout        */
  111. char    *LogFile;
  112. char    XDebug;
  113. long    NilFH;
  114. short    CronFileExists = 1; /*    Does the crontab file exist?        */
  115.  
  116. void    logmessage();
  117.  
  118. void
  119. main(ac, av)
  120. short ac;
  121. char *av[];
  122. {
  123.     PROC *proc = FindTask(NULL);
  124.  
  125.     Enable_Abort = 0;
  126.     {
  127.     register short i;
  128.     short j = 0;
  129.  
  130.     for (i = 1; i < ac; ++i) {
  131.         register char *ptr = av[i];
  132.         if (*ptr != '-') {
  133.         switch(j++) {
  134.         case 0:
  135.             LogFile = ptr;
  136.             break;
  137.         }
  138.         continue;
  139.         }
  140.         while (*++ptr) {
  141.         switch(*ptr) {
  142.         case 'd':
  143.             ++XDebug;
  144.             break;
  145.         default:
  146.             WriteStr(Output(), "bad option\n");
  147.             goto fail;
  148.         }
  149.         }
  150.     }
  151.     }
  152.     if (!LogFile) {
  153. fail:
  154.     WriteStr(Output(), "DCron [-d] Logfile\n");
  155.     WriteStr(Output(), "DCron, V1.05\n");
  156.     exit(1);
  157.     }
  158.     if (OpenDevice("timer.device", 0, &Iot, UNIT_VBLANK)) {
  159.     logmessage("Unable to open timer.device\n");
  160.     exit(1);
  161.     }
  162.     if (!DeviceProc("NULL:")) {
  163.     logmessage("NULL: device required for dcron to run\n");
  164.     WriteStr(Output(), "NULL: device required to run\n");
  165.     exit(1);
  166.     }
  167.     proc->pr_ConsoleTask = DeviceProc("NULL:");
  168.     fclose(stderr);
  169.     logmessage("Startup: V1.05 (dist: 22 December 1988)\n");
  170.  
  171.     NilFH = Open("null:", 1006);
  172.     DateStamp(&EndDate);
  173.     DateStamp(&ModDate);
  174.     TPort = CreatePort(NULL,0);
  175.     Iot.tr_time.tv_secs = 1;
  176.     Iot.tr_time.tv_micro= 0;
  177.     Iot.tr_node.io_Message.mn_ReplyPort = TPort;
  178.     Iot.tr_node.io_Command = TR_ADDREQUEST;
  179.     Iotmin = Iot;
  180.     SendIO(&Iot);
  181.     SendIO(&Iotmin);
  182.     for (;;) {
  183.     long mask;
  184.     mask = Wait(SIGS | (1 << TPort->mp_SigBit));
  185.     if (mask & (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D)) {
  186.         logmessage("DCRON: Break\n");
  187.         break;
  188.     }
  189.     if (mask & (SIGBREAKF_CTRL_E|SIGBREAKF_CTRL_F)) {
  190.         logmessage("^E/F force scan\n");
  191.         AbortIO(&Iot);          /*  force execution                     */
  192.     }
  193.     if (FatalError)
  194.         break;
  195.     if (CheckIO(&Iotmin)) {     /*  if file/date modified, force exec.  */
  196.         WaitIO(&Iotmin);
  197.         if (datemod() + filemod())
  198.         AbortIO(&Iot);
  199.         Iotmin.tr_time.tv_secs = 60;
  200.         Iotmin.tr_time.tv_micro= 0;
  201.         SendIO(&Iotmin);
  202.     }
  203.     if (CheckIO(&Iot)) {
  204.         DATESTAMP Ds;
  205.         short secmin;
  206.  
  207.         WaitIO(&Iot);
  208.         dostuff();
  209.         if (NextTimeout <= 0)
  210.         NextTimeout = 1;
  211.         if (XDebug) {
  212.         logmessage(" Next Timeout in %02ld:%02ld\n",
  213.             NextTimeout / 60,
  214.             NextTimeout % 60
  215.         );
  216.         }
  217.         DateStamp(&Ds);
  218.         secmin = 61 - (Ds.ds_Tick / 50);    /*  1+Secs till next min. */
  219.         Iot.tr_time.tv_secs = secmin + (NextTimeout - 1) * 60;
  220.         Iot.tr_time.tv_micro= 0;
  221.         SendIO(&Iot);
  222.     }
  223.     }
  224.     AbortIO(&Iot);
  225.     AbortIO(&Iotmin);
  226.     WaitIO(&Iot);
  227.     WaitIO(&Iotmin);
  228.     CloseDevice(&Iot);
  229.     DeletePort(TPort);
  230.     Close(NilFH);
  231. }
  232.  
  233. /*
  234.  *  If the current date is less than the previous date
  235.  *  If the current date is more than +5 minutes the previous date
  236.  */
  237.  
  238. datemod()
  239. {
  240.     DATESTAMP Date;
  241.     long xold, xnew;
  242.  
  243.     DateStamp(&Date);
  244.     xold = ModDate.ds_Days * 1440 + ModDate.ds_Minute;
  245.     xnew = Date.ds_Days * 1440 + Date.ds_Minute;
  246.     ModDate = Date;
  247.     if (xnew < xold || xnew - 5 > xold) {
  248.     DateStamp(&EndDate);
  249.     logmessage("Date change noted\n");
  250.     return(1);
  251.     }
  252.     return(0);
  253. }
  254.  
  255. /*
  256.  *  If the file modification time is different than when
  257.  *  we last checked, we want to recalculate our timeout
  258.  *  and rescan it.
  259.  */
  260.  
  261. filemod()
  262. {
  263.     char buf[sizeof(FIB)+4];
  264.     register FIB *fib = (FIB *)(((long)buf+3)&~3);
  265.     long lock;
  266.     long result = 0;
  267.  
  268.     if (lock = Lock(CRONTAB, SHARED_LOCK)) {
  269.     if (Examine(lock, fib)) {
  270.         if (fib->fib_Date.ds_Tick   != TabDate.ds_Tick ||
  271.         fib->fib_Date.ds_Minute != TabDate.ds_Minute ||
  272.         fib->fib_Date.ds_Days    != TabDate.ds_Days) {
  273.  
  274.         if (TabDate.ds_Days) {
  275.             logmessage("crontab modification noted\n");
  276.             result = 1;
  277.         }
  278.         TabDate = fib->fib_Date;
  279.         }
  280.     }
  281.     UnLock(lock);
  282.     }
  283.     return(result);
  284. }
  285.  
  286. /*
  287.  *  DOSTUFF()
  288.  *
  289.  *  Scan the CRONTAB and execute any entries that fall between
  290.  *  BegDate and EndDate.  Specifically, >= BegDate and < EndDate.
  291.  *
  292.  *  This is done as little as possible.  While scanning, we calculate the
  293.  *  timeout period till the next entry and will attempt to sleep for that
  294.  *  period of time before comming back to this routine.  Checks are still
  295.  *  done every 60 seconds for radical date changes and crontab modification
  296.  *  (routines above).
  297.  *
  298.  *  The calculation of the timeout period is not entirely accurate.
  299.  *  Specifically, in cases similar to:
  300.  *
  301.  *    "30 16 * * *"
  302.  *
  303.  *  If the current time is 16:xx, the timeout period calculated for the hours
  304.  *  will 0 instead of 24 (if the minutes had been '*', this would be
  305.  *  correct.  But if it is 16:31 the system will wait 29 minutes instead of
  306.  *  23 hours and 30 minutes.  Of course, it will re-scan at +29 minutes and
  307.  *  not find anything to do, but you get the point.
  308.  */
  309.  
  310. static char dim[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  311. static CARRY CArry;
  312. static short CRanStart[] = {  0,  0,  1  ,  1, 0 };
  313. static short CRanEnd[]     = { 59, 23, 31  , 12, 6 };
  314. static long  CWeight[]     = {  1, 60, 1440, 28*1440, 1440 };
  315. static char *CBase[]     = { CArry.min, CArry.hour, CArry.day, CArry.month, CArry.dow };
  316.  
  317. dostuff()
  318. {
  319.     CDATE cdate;
  320.     long fh;
  321.     char buf[256];
  322.     int ie;
  323.  
  324.     NextTimeout = -1;
  325.  
  326.     BegDate = EndDate;
  327.     DateStamp(&EndDate);
  328.     breakup(&EndDate, &cdate.End);          /*  Breakup the time            */
  329.     breakup(&BegDate, &cdate.Beg);          /*  note: dim[FEB] is modified  */
  330.     bzero(&CArry, sizeof(CArry));           /*  range array                 */
  331.  
  332.     /*
  333.      *    Generate the range for the delta time that has occured since the
  334.      *    last reading.  (A) This is used to ensure we don't miss anything
  335.      *               (B) Ensure we don't repeat anything
  336.      */
  337.  
  338.     ie = makerange(CArry.min  , 0, 59, cdate.Beg.min  , cdate.End.min  ,  1);
  339.     ie = makerange(CArry.hour , 0, 23, cdate.Beg.hour , cdate.End.hour , ie);
  340.     ie = makerange(CArry.day  , 1, dim[cdate.Beg.month-1],
  341.                        cdate.Beg.day  , cdate.End.day  , ie);
  342.     ie = makerange(CArry.month, 1, 12, cdate.Beg.month, cdate.End.month, ie);
  343.     ie = makerange(CArry.dow  , 0,  6, cdate.Beg.dow  , cdate.End.dow  , ie);
  344.  
  345.     /*
  346.      *    Note:    if ie is set after this then dostuff() was called
  347.      *        within the same minute segment and would yield a
  348.      *        repeation of commands.
  349.      */
  350.  
  351.     if (ie == 0) {
  352.     ReadLn(NULL, NULL, 0);
  353.     if (fh = Open(CRONTAB, 1005)) {
  354.         long whennext;                /*  in minutes, estimate    */
  355.  
  356.         if (CronFileExists == 0) {
  357.         CronFileExists = 1;
  358.         logmessage("DCron File %s exists\n", CRONTAB);
  359.         }
  360.         while (ReadLn(fh, buf, 256)) {
  361.         if (buf[0] == 0 || buf[0] == '#')
  362.             continue;
  363.         whennext = handleline(buf, &cdate.End); /*  take smallest           */
  364.         if (XDebug > 1)
  365.             logmessage("whennext == %ld\n", whennext);
  366.         if (NextTimeout < 0 || whennext < NextTimeout)
  367.             NextTimeout = whennext;
  368.         }
  369.         Close(fh);
  370.     } else {
  371.         if (CronFileExists == 1) {
  372.         CronFileExists = 0;
  373.         logmessage("Unable to open %s\n", CRONTAB);
  374.         }
  375.     }
  376.     }
  377.  
  378.     /*
  379.      *    You can't trust the Amiga's clock vs the timer.device ... this is to
  380.      *    ensure that they don't get too far off... no more than an hour timeout
  381.      *    is allowed.  Frankly, anything above 30 minutes will yield unnoticeable
  382.      *    results.
  383.      */
  384.  
  385.     if (--NextTimeout > 60)
  386.     NextTimeout = 60;
  387. }
  388.  
  389. breakup(date, mhd)
  390. DATESTAMP *date;
  391. MHDMW *mhd;
  392. {
  393.     long days;
  394.     long years;
  395.     char leap;
  396.     short month;
  397.  
  398.     days = date->ds_Days + 731;         /*    1976        */
  399.     years = days / (365*3+366);             /*  #quad yrs   */
  400.     days -= years * (365*3+366);
  401.     leap = (days < 366);                    /*  is a leap yr*/
  402.     years = 1976 + 4 * years;
  403.     dim[1] = 29;
  404.     if (!leap) {
  405.     dim[1] = 28;
  406.     days -= 366;
  407.     ++years;
  408.     }
  409.     years += days / 365;
  410.     days -= (days / 365) * 365;
  411.     for (month = 0; (month==1) ? (days >= 28 + leap) : (days >= dim[month]); ++month)
  412.     days -= (month==1) ? (28 + leap) : dim[month];
  413.     mhd->min    = date->ds_Minute % 60;
  414.     mhd->hour    = date->ds_Minute / 60;
  415.     mhd->day    = days + 1;
  416.     mhd->month    = month + 1;
  417.     mhd->dow    = date->ds_Days % 7;    /*  0 = sunday     */
  418. }
  419.  
  420. /*
  421.  *  S < RANGE <= E    (if previous entries were equal)
  422.  *        note: if S == E, E is still included
  423.  *  S <= RANGE <= E    (if previous entries were not equal)
  424.  */
  425.  
  426. makerange(ptr, cs, ce, s, e, isequal)
  427. register char *ptr;
  428. {
  429.     long error = 5000;
  430.     short oldisequal = isequal;
  431.  
  432.     isequal = (isequal && s == e);
  433.     if (oldisequal && s != e) {
  434.     if (++s > ce)
  435.         s = cs;
  436.     }
  437.     while (--error && s != e) {
  438.     ptr[s++] = 1;
  439.     if (s > ce)
  440.         s = cs;
  441.     }
  442.     ptr[e] = 1;
  443.     if (error == 0) {
  444.     logmessage("DCRON,makerange(): software error\n");
  445.     FatalError = 1;
  446.     }
  447.     return(isequal);
  448. }
  449.  
  450. /*
  451.  *  This is the core of the routine.  The fields are interpreted, times
  452.  *  estimated, ready entries run, etc..
  453.  *
  454.  *  order:  minutes hours days months dayofweek
  455.  *
  456.  *  Each field may be a * indicating all-times, and a combination of a
  457.  *  range and comma delimited numbers:    0-3,45,46-49        (MUST BE SORTED)
  458.  *
  459.  *  Each field is separated by one or spaces+tabs
  460.  */
  461.  
  462. handleline(buf, endt)
  463. char *buf;
  464. MHDMWARY *endt;
  465. {
  466.     register char *ptr = buf;
  467.     register short i;
  468.     char *gnum();
  469.     short sumok = 0;
  470.     long howsoon = 0;            /*    how soon until next entry */
  471.  
  472.     for (i = 0; i < 5; ++i) {       /*  5 arguments     */
  473.     char ok = 0;
  474.     short nextunit = -1;        /*    count, in units */
  475.  
  476.     while (*ptr == ' ' || *ptr == 9)
  477.         ++ptr;
  478.     while (*ptr && *ptr != 9 && *ptr != ' ') {
  479.         short start;
  480.         short finish;
  481.         if (*ptr == '*') {
  482.         ++ptr;
  483.         ok = 1;
  484.         break;
  485.         }
  486.         ptr = gnum(ptr, &start);
  487.         finish = start;
  488.         if (*ptr == '-')
  489.         ptr = gnum(ptr+1, &finish);
  490.         if (*ptr == ',')
  491.         ++ptr;
  492.  
  493.         /*
  494.          *    Determine if current date is within time range.  nextunit
  495.          *    is the number of time units for this index till the next
  496.          *    entry will be executed.
  497.          */
  498.  
  499.         if (endt->entry[i] < start) {
  500.         register short newnextunit = start - endt->entry[i];
  501.         if (nextunit < 0 || newnextunit < nextunit)
  502.             nextunit = newnextunit;
  503.         } else
  504.         if (endt->entry[i] > finish) {
  505.         register short newnextunit = 1 + CRanEnd[i] - endt->entry[i] + start - CRanStart[i];
  506.         if (nextunit < 0 || newnextunit < nextunit)
  507.             nextunit = newnextunit;
  508.         } else
  509.         nextunit = 0;  /*  Inside time range   */
  510.  
  511.         if (start < CRanStart[i] || finish < CRanStart[i] || start > CRanEnd[i] || finish > CRanEnd[i]) {
  512.         logmessage("Illegal Bounds in: %s\n", buf);
  513.         logmessage(" Value %ld & %ld bounds %ld-%ld ", start, finish, CRanStart[i], CRanEnd[i]);
  514.         logmessage("  AT: '%s'\n", ptr);
  515.         continue;
  516.         } else {
  517.         do {
  518.             if (start > CRanEnd[i])
  519.             start = CRanStart[i];
  520.             if (CBase[i][start]) {
  521.             ok = 1;
  522.             break;
  523.             }
  524.             ++start;
  525.         } while (start != finish + 1);
  526.         }
  527.     }
  528.     if (ok)
  529.         ++sumok;
  530.     if (nextunit > 0)
  531.         howsoon += nextunit * CWeight[i];
  532.     }
  533.  
  534.     /*
  535.      *    This is tricky.  Since this program also handles the dummy-null
  536.      *    device, it CANNOT block on the execute.  The only way to do that
  537.      *    is to run <nil: >nil: so run itself does not attempt to access
  538.      *    "*".  It is ok if the program run runs accesses "*" as we will not
  539.      *    be frozen then.
  540.      */
  541.  
  542.     if (sumok == i) {
  543.     register char *p2 = malloc(strlen(ptr)+32);
  544.  
  545.     logmessage("%s\n", buf);
  546.     while (*ptr == ' ' || *ptr == 9)
  547.         ++ptr;
  548.     /*      strcpy(p2, "run <nil: >nil: ");   */
  549.     strcpy(p2, "run ");
  550.     strcat(p2, ptr);
  551.     Execute(p2, NilFH, NilFH);
  552.     }
  553.     if (XDebug > 2) {
  554.     logmessage("FOR %-20s   In %ld %02ld:%02ld\n",
  555.         buf, howsoon / 1440, howsoon / 60 % 24, howsoon % 60
  556.     );
  557.     }
  558.     return(howsoon);
  559. }
  560.  
  561. /*
  562.  *  Poor man's log.  Note that the log file is not left open ... this allows
  563.  *  one to read or tail it at any time.
  564.  */
  565.  
  566. void
  567. logmessage(ptr, a, b, c, d, e)
  568. char *ptr;
  569. {
  570.     char *buf = malloc(512);
  571.     DATESTAMP date;
  572.     MHDMW   cb;
  573.     long    fh;
  574.     static char *Dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  575.     static char *Miy[] = { "---", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  576.                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
  577.  
  578.     if (!buf)
  579.     return;
  580.  
  581.     DateStamp(&date);
  582.     breakup(&date, &cb);
  583.  
  584.     sprintf(buf, "dcron: %s %2ld %s %02ld:%02ld   ",
  585.     Dow[cb.dow], cb.day, Miy[cb.month], cb.hour, cb.min
  586.     );
  587.     sprintf(buf+strlen(buf), ptr, a, b, c, d, e);
  588.     if ((fh = Open(LogFile, 1005)) == NULL)
  589.     fh = Open(LogFile, 1006);
  590.     if (fh) {
  591.     Seek(fh, 0L, 1);
  592.     WriteStr(fh, buf);
  593.     Close(fh);
  594.     }
  595.     free(buf);
  596. }
  597.  
  598. /*
  599.  *  Scan a string for a value, return the new pointer position and set the
  600.  *  specified short pointer to the value.
  601.  */
  602.  
  603. char *
  604. gnum(ptr, pval)
  605. register char *ptr;
  606. register short *pval;
  607. {
  608.     short val = 0;
  609.     if (*ptr < '0' || *ptr > '9')
  610.     logmessage("Not a number: %s\n", ptr);
  611.     while (*ptr >= '0' && *ptr <= '9') {
  612.     val = val * 10 + *ptr - '0';
  613.     ++ptr;
  614.     }
  615.     *pval = val;
  616.     return(ptr);
  617. }
  618.  
  619. WriteStr(fh, buf)
  620. long fh;
  621. char *buf;
  622. {
  623.     Write(fh, buf, strlen(buf));
  624. }
  625.  
  626. ReadLn(fh, buf, max)
  627. long fh;
  628. char *buf;
  629. short max;
  630. {
  631.     static char Data[1024];
  632.     static short RIdx, RLen;
  633.     register short i;
  634.  
  635.     if (fh == NULL) {
  636.     RIdx = RLen = 0;
  637.     return(0);
  638.     }
  639.     for (--max, i = 0; i < max; ++i) {
  640.     if (RIdx == RLen) {
  641.         RLen = Read(fh, Data, 1024);
  642.         RIdx = 0;
  643.         if (RLen <= 0) {
  644.         buf[i] = 0;
  645.         return(0);
  646.         }
  647.     }
  648.     if ((buf[i] = Data[RIdx++]) == '\n')
  649.         break;
  650.     }
  651.     buf[i] = 0;
  652.     return(1);
  653. }
  654.  
  655. SHAR_EOF
  656. cat << \SHAR_EOF > dcron.doc
  657.  
  658.                 DCRON.DOC
  659.  
  660.                   UNIX-like CRON
  661.  
  662.                   V1.05
  663.                  22 December 1988
  664.  
  665.                   Matthew Dillon
  666.                   891 Regal Rd
  667.                   Berkeley, Ca. 94708
  668.  
  669.                  dillon@ucbvax.Berkeley.edu
  670.                  ...!ucbvax!dillon
  671.  
  672. DESCRIPTION:
  673.  
  674.     Those not familar with the UNIX cron command probably do not need it.
  675.  
  676.  
  677. INSTALLATION:
  678.     the NULL: device must be mounted
  679.     an s:crontab file (see instructions below)
  680.     1.3 RUN required so you can: run <nil: >nil: dcron <logfile>
  681.  
  682.     copy null-handler l:
  683.     join devs:Mountlist Mountlist as temp
  684.     copy temp devs:Mountlist
  685.     delete temp
  686.     copy dcron c:        (or wherever)
  687.     copy crontab s:        (modify to your like)
  688.  
  689. INSTRUCTIONS:
  690.  
  691.  
  692.     NOTE:   * All cron-commands are automatically RUN  in the background,
  693.           you do not need to say 'run' in the command argument of the
  694.           s:crontab file
  695.  
  696.         * You do not have to redirect standard command output.  I.E.
  697.           if you do a 'List' from cron, the output is sent to the
  698.           cia.
  699.  
  700.         * DCRON WORKS ONLY WITH 1.3, specifically, the 1.3 RUN command
  701.           must be installed.
  702.  
  703.         * DO NOT RUNBACK CRON!!!  Use: run <nil: >nil:
  704.  
  705.  
  706.     Inspired by Rick Schaeffer's AmigaCron.  I decided to write this
  707. one from scratch.  The program is crontab file compatible with Rick's
  708. and the UNIX cron tab format with the same exceptions as Rick had.
  709. Specifically, the % char is not implemented and the day-of-week is
  710. ranged 0-6 (Sun-Sat) rather than the UNIX 1-7 (Mon-Sun).
  711.  
  712.     The major purpose of this program, apart from yielding the functionality
  713. of cron, is the addition of major time optimizations.  The program does
  714. not scan the S:crontab file every 60 seconds if it doesn't need to.  It
  715. *does* check certain things every 60 seconds like whether the system time
  716. has undergone a drastic change or the s:crontab file has been modified
  717. (yielding a re-scan).  Under normal operation, the program will sleep via
  718. the timer.device for up to an hour between re-scans.  It does this by
  719. calculating the delta time to the next required program execution and
  720. sleeping to just before that time occurs.
  721.  
  722.     The result is that my cron takes virtually no CPU.
  723.  
  724.     My version also fixes several continuity bugs in Rick's (sorry Rick!).
  725. Specifically, commands will never be accidently run twice in a single
  726. minute period and unless the system time is modified, no command will
  727. ever be skipped (due to the cron program missing that particular minute).
  728.  
  729.     [run <nil: >nil:] DCron [-d] logfile
  730.  
  731.     Using the 1.3 RUN command and redirecting both stdin and stdout to NIL:,
  732.     you can safely close the CLI window that you ran DCron in.
  733.  
  734.     The logfile is a required argument and logs all errors and status
  735. messages.  Under normal conditions this file does not get very big.
  736. Cron does an open-append/write/close sequence every time it logs a message
  737. allowing you to cat, edit, or even remove the file with cron running.
  738. (If you remove it, the next logged message will cause the file to be
  739. re-created).
  740.  
  741.     The -d option, for debug, is used for debugging and logs additional
  742. information.
  743.  
  744.     The control file is S:CRONTAB and may contain one of three types of
  745. lines:    (1) A blank line, (2) A line beginning with a hash ``#'' which
  746. introduces a comment, (3) A cron line of 5 or 6 fields:
  747.  
  748. <min> <hour> <day> <month> <dayofweek>    [<command>]
  749.  
  750.     Each field <min> to <dayofweek> can be either a single asterix ``*''
  751. which matches all times, or a comma delimited list of numbers or ranges
  752. which must be in numerical order.  Assume the control file is scanned
  753. every minute.  On every minute, each field that matches the current
  754. time, day, and day of week will be executed.
  755.  
  756.     The <command> field is optional.  If no command is given the date is
  757. simply logged to the logfile (as well as the cron line that caused it).
  758. If a command is given, it is a CLI command and is automatically RUN
  759. (asynchronous from the cron task).  Standard in/out is NIL:  The cron line
  760. that ran the command is logged.
  761.  
  762. * * * * *   Date
  763.  
  764.     Would execute the Date command every minute.  The output is ignored.
  765.     I.E. useless.  Also, running something every minute wastes CPU.
  766.  
  767. 0-3,5,7,40-50 * * * *    Date
  768.  
  769.     Would execute it at the hour, 1 past, 2 past, 3 past, 5 past, 7 past,
  770.     40 41 42 ... and 50 past every hour.  Combinations can be used to
  771.     yield more interesting results:
  772.  
  773. 0 0,12 * * 0    Date
  774.  
  775.     Would run Date at midnight and 12 noon, but only on sunday.
  776.  
  777. 0 0 25 12 *    Echo >ram:x "Merry Cristmas"
  778.  
  779.     Would execute the Echo command at midnight beginning christmas day and
  780.     stick it in ram:x.
  781.  
  782. * * * * 2-3 Date
  783.  
  784.     Is useless ... execute the Date command every minute but only if the
  785.     day is a tuesday or wednesday (i.e. 1440 times on tuesday, 1440
  786.     times on wednesday, nothing for any other day).
  787.  
  788. 0 4 * * 1     shell >ram:backups -c "ChangeTaskPri -1;dobackups"
  789.  
  790.     Would run a shell script to do my HD backup at 4:00 A.M. every Monday.
  791. (I have the advantage of having two physical drives and can back one up
  792. to the other without user intervention).  This would also be useful to
  793. backup something to a remote host if you are on a network.
  794.  
  795.  
  796. SHAR_EOF
  797. cat << \SHAR_EOF > Mountlist
  798.  
  799. NULL:       Handler = L:null-handler
  800.        Stacksize = 1024
  801.        Priority = 5
  802.        GlobVec = 1
  803. #
  804.  
  805. SHAR_EOF
  806. cat << \SHAR_EOF > dcron.uu
  807.  
  808. begin 644 dcron
  809. M```#\P`````````#``````````(```D<```"&P````$```/I```)'$[Z$7).H
  810. M5?_J2.<(($*G3KHC9EA/*T#__$*LB!A";?_Z>`%@8#`$2,#E@"!M``PD<`@`A
  811. M#!(`+6<8,"W_^E)M__I(P&`&*4J(#F`$2H!G]F`R4HI*$F<L$!)(@$C`8!A2S
  812. M+(@28!Q(>@+B3KHA<B\`3KH/HE!/8!J0O````&1GX&#D8,Y21+AM``IMFDJLO
  813. MB`YF*DAZ`L).NB%&+P!.N@]V4$](>@+&3KHA-B\`3KH/9E!/2'@``4ZZ'MA8!
  814. M3TAX``%(;(>V0J=(>@*P3KHC`D_O`!!*@&<42'H"K4ZZ#4!83TAX``%.NAZJ<
  815. M6$](>@*V3KH@@EA/2H!F)$AZ`JY.N@T>6$](>@+,3KH@UB\`3KH/!E!/2'@`+
  816. M`4ZZ'GA83TAZ`M!.NB!06$\@;?_\(4``I$AL@5Q.NAH:6$](>@*Z3KH,WEA/1
  817. M2'@#[DAZ`M5.NB"`4$\I0(@42&R'DDZZ(`!83TALAYY.NA_V6$]"IT*G3KHA*
  818. M#E!/*4"(!BE\`````8?60JR'VBELB`:'Q#E\``F'TD'LA]Y#[(>V<`D@V5'($
  819. M__Q(;(>V3KHB:%A/2&R'WDZZ(EY83R!LB`9P`!`H``]R`>&A@KP``/``+P%.?
  820. MNB)<6$\K0/_X("W_^,"\```P`&<.2'H"0TZZ##A83V```1(@+?_XP+P``,``3
  821. M9Q1(>@(W3KH,'EA/2&R'MDZZ("A83TILB`IF``#J2&R'WDZZ(#I83TJ`9SI(D
  822. M;(?>3KHB"EA/3KH",B\`3KH"O"(?TH!G"DALA[9.NA_N6$\I?````#R'_D*LB
  823. MB`)(;(?>3KHAM%A/2&R'MDZZ'_)83TJ`9P``CDALA[9.NB'`6$].N@,X2FR(9
  824. M#&X&.7P``8@,2BR($F<H,"R(#$C`<CQ.NA?.+P`P+(@,2,!R/$ZZ%Y@O`$AZY
  825. M`9A.N@MN3^\`#$AM_^Q.NAZ@6$]R,B`M__1.NA=V<CV2@#M!_^HP+(@,2,!3;
  826. M@'(\3KH>/C(M_^I(P="!*4"'UD*LA]I(;(>V3KHA&%A/8`#^N$ALA[9.NA\N^
  827. M6$](;(?>3KH?)%A/2&R'MDZZ(1Q83TALA]Y.NB$26$](;(>V3KH?-EA/+RR(B
  828. M!DZZ']183R\LB!1.NAX`6$],WP003EU.=6)A9"!O<'1I;VX*`$1#<F]N(%LM!
  829. M9%T@3&]G9FEL90H`1$-R;VXL(%8Q+C`U"@!T:6UE<BYD979I8V4`56YA8FQE)
  830. M('1O(&]P96X@=&EM97(N9&5V:6-E"@!.54Q,.@!.54Q,.B!D979I8V4@<F5Q@
  831. M=6ER960@9F]R(&1C<F]N('1O(')U;@H`3E5,3#H@9&5V:6-E(')E<75I<F5D^
  832. M('1O(')U;@H`3E5,3#H`4W1A<G1U<#H@5C$N,#4@*&1I<W0Z(#(R($1E8V5M6
  833. M8F5R(#$Y.#@I"@!N=6QL.@!$0U)/3CH@0G)E86L*`%Y%+T8@9F]R8V4@<V-AX
  834. M;@H`($YE>'0@5&EM96]U="!I;B`E,#)L9#HE,#)L9`H`3E7_[$AM__1.NASP1
  835. M6$\B/```!:`@+(>>3KH<H-"LAZ(K0/_P(CP```6@("W_]$ZZ'(K0K?_X*T#_M
  836. M[$'LAYY#[?_T(-D@V2#9("W_[+"M__!M#"`M_^Q;@+"M__!O&DALAY).NAR6X
  837. M6$](>@`23KH)3EA/<`%.74YU<`!@^$1A=&4@8VAA;F=E(&YO=&5D"@``3E7^1
  838. M\"\*0>W^^"`(5H#`O/____PD0$*M_O!(>/_^2'H`>DZZ'*903RM`_O1G8B\*"
  839. M+RW^]$ZZ'%A03TJ`9T@@*@",L*R'LF84("H`B+"LAZYF"B`J`(2PK(>J9RI*0
  840. MK(>J9Q)(>@!`3KH(Q%A/*WP````!_O!![(>J(DK3_````(0@V2#9(-DO+?[T_
  841. M3KH<DEA/("W^\"1?3EU.=7,Z8W)O;G1A8@!C<F]N=&%B(&UO9&EF:6-A=&EOP
  842. M;B!N;W1E9`H`3E7^X#E\__^(#$'LAX9#[(>2(-D@V2#92&R'DDZZ&XQ83TAM_
  843. M__9(;(>23KH"2%!/2&W_[$ALAX9.N@(Z4$](>`"(2&R"\DZZ"M)03TAX``$P;
  844. M+?_V2,`O`#`M_^Q(P"\`2'@`.T*G2&R"\DZZ`XQ/[P`8*T#^Y"\M_N0P+?_XP
  845. M2,`O`#`M_^Y(P"\`2'@`%T*G2&R#+DZZ`V)/[P`8*T#^Y"\M_N0P+?_Z2,`OX
  846. M`#`M__!(P"\`,"W_\D'L@`,2,```2(%(P2\!2'@``4AL@T9.N@,H3^\`&"M`W
  847. M_N0O+?[D,"W__$C`+P`P+?_R2,`O`$AX``Q(>``!2&R#9DZZ`OQ/[P`8*T#^C
  848. MY"\M_N0P+?_^2,`O`#`M__1(P"\`2'@`!D*G2&R#<TZZ`M)/[P`8*T#^Y$JM)
  849. M_N1F``#00J="IT*G3KH)2$_O``Q(>`/M2'H`T$ZZ&LQ03RM`_^AG``"22FR`&
  850. M`F84.7P``8`"2'H`TDAZ`+A.N@;Z4$](>`$`2&W^Z"\M_^A.N@D$3^\`#$J`Q
  851. M9U)*+?[H9^(,+0`C_NAGVDAM__9(;?[H3KH#*E!/*T#^X`PL``&($F\.+RW^N
  852. MX$AZ`(I.N@:L4$]*;(@,;0XP+(@,2,`B+?[@LH!L!CEM_N*(#&"6+RW_Z$ZZN
  853. M&:A83V`:#&P``8`"9A)";(`"2'H`<$AZ`%E.N@9J4$]3;(@,#&P`/(@,;P8YR
  854. M?``\B`Q.74YU<SIC<F]N=&%B`$1#<F]N($9I;&4@)7,@97AI<W1S"@!S.F-R*
  855. M;VYT86(`=VAE;FYE>'0@/3T@)6QD"@!5;F%B;&4@=&\@;W!E;B`E<PH`<SIC\
  856. M<F]N=&%B`$Y5__0@;0`((!#0O````MLK0/_\(CP```6U("W__$ZZ$?@K0/_X#
  857. M(CP```6U("W_^$ZZ&,21K?_\#*T```%N__QL!'`!8`)P`!M`__<@+?_XY8#09
  858. MO```![@K0/_X&7P`'8`%2BW_]V82&7P`'(`%!*T```%N__Q2K?_X(CP```%M,
  859. M("W__$ZZ$9+1K?_X(CP```%M("W__$ZZ$8`B/````6U.NAA4D:W__$)M__1@0
  860. M,@QM``'_]&80$"W_]TB`2,#0O````!Q@$C`M__1![(`$,@`0,!``2(!(P)&M#
  861. M__Q2;?_T#&T``?_T9AX0+?_W2(!(P-"\````'"(M__RR@&T$<`%@`G``8!XPG
  862. M+?_T0>R`!!(P``!(@4C!("W__+"!;01P`6`"<`!FB"!M``@@*``$<CQ.NA$.V
  863. M(&T`##"`(&T`""`H``1R/$ZZ$-(@;0`,,4```B`M__Q2@"!M``PQ0``$(&T`#
  864. M##`M__120#%```8@;0`((!!R!TZZ$,H@;0`,,4``"$Y=3G5.5?_Z+PHD;0`(S
  865. M*WP``!.(__P[;0`>__I*K0`<9Q0@+0`4L*T`&&8**WP````!`!Q@!$*M`!Q*-
  866. M;?_Z9QX@+0`4L*T`&&<44JT`%"`M`!2PK0`0;P8K;0`,`!13K?_\9RH@+0`4W
  867. ML*T`&&<@("T`%%*M`!05O``!"``@+0`4L*T`$&\&*VT`#``48-`@+0`8%;P`K
  868. M`0@`2JW__&802'H`&$ZZ`]183SE\``&("B`M`!PD7TY=3G5$0U)/3BQM86MES
  869. M<F%N9V4H*3H@<V]F='=A<F4@97)R;W(*``!.5?_R2.<,,"1M``A";?_^0JW_I
  870. M^G@`8``"6D(M__D[?/____8,$@`@9P8,$@`)9@12BF#P2A)G``(*#!(`"6<`$
  871. M`@(,$@`@9P`!^@P2`"IF#%**&WP``?_Y8``!Z$AM__0O"DZZ!+I03R1`.VW_D
  872. M]/_R#!(`+6822&W_\B!*4H@O"$ZZ!)Q03R1`#!(`+&8"4HHP!$C`XX`@;0`,Z
  873. M,C`(`+)M__1L)#`$2,#C@"!M``PZ+?_TFG`(`$IM__9M!KIM__9L!#M%__9@P
  874. M:C`$2,#C@"!M``PR,`@`LFW_\F]2,`1(P..`0>R`&C(P"`!(P5*!,`1(P..`H
  875. M(&T`##0P"`!(PI*","W_]$C`TH`P!$C`XX!![(`0-#`(`$C"DH(Z`4IM__9M'
  876. M!KIM__9L!#M%__9@!$)M__8P!$C`XX!![(`0,BW_]+)P"`!M/#`$2,#C@$'L<
  877. M@!`R+?_RLG`(`&TH,`1(P..`0>R`&C(M__2R<`@`;A0P!$C`XX!![(`:,BW_'
  878. M\K)P"`!O7B\M``A(>@&F3KH"$%!/,`1(P..`0>R`&C(P"`!(P2\!,`1(P..`7
  879. M0>R`$#(P"`!(P2\!,"W_\DC`+P`P+?_T2,`O`$AZ`7].N@'23^\`%"\*2'H!7
  880. MDDZZ`<103V``_E8P!$C`XX!![(`:,BW_]+)P"`!O$#`$2,#C@$'L@!`[<`@`B
  881. M__0P!$C`Y8!![(`X(C`(`#`M__1(P"!`2C`8`&<(&WP``?_Y8!92;?_T,"W_@
  882. M\DC`4H`R+?_T2,&P@6:B8`#]]$HM__EG!%)M__Y*;?_V;QXP!$C`Y8!![(`DR
  883. M(@`@,!@`,BW_]DC!3KH4,-&M__I21+A\``5M`/VB,"W__K!$9EPO"DZZ!%Y80
  884. M3R!`2&@`($ZZ$+Y83R9`+RT`"$AZ`-1.N@#Z4$\,$@`@9P8,$@`)9@12BF#PE
  885. M2'H`OB\+3KH$%E!/+PHO"TZZ![I03R\LB!0O+(@4+PM.NA0X3^\`#`PL``*(>
  886. M$F\\<CP@+?_Z3KH,]B\`<CP@+?_Z3KH,PG(83KH,Y"\`(CP```6@("W_^DZZV
  887. M#*PO`"\M``A(>@!A87Y/[P`4("W_^DS?##!.74YU26QL96=A;"!";W5N9',@?
  888. M:6XZ("5S"@`@5F%L=64@)6QD("8@)6QD(&)O=6YD<R`E;&0M)6QD(``@($%4W
  889. M.B`G)7,G"@`E<PH`<G5N(`!&3U(@)2TR,',@("!);B`E;&0@)3`R;&0Z)3`RS
  890. M;&0*``!.5?_B2'@"`$ZZ#ZI83RM`__Q*K?_\9@1.74YU2&W_\$ZZ$QQ83TAMO
  891. M_^9(;?_P3KKYV%!/,"W_YDC`+P`P+?_H2,`O`#`M_^Q(P.6`0>R`:"\P"``P6
  892. M+?_J2,`O`#`M_^Y(P.6`0>R`3"\P"`!(>@#R+RW__$ZZ!W!/[P`<+RT`'"\M<
  893. M`!@O+0`4+RT`$"\M``PO+0`(+RW__$ZZ`JY83]"M__PO`$ZZ!T!/[P`<2'@#)
  894. M[2\LB`Y.NA+Z4$\K0/_B9A)(>`/N+RR(#DZZ$N903RM`_^)*K?_B9RI(>``!,
  895. M0J<O+?_B3KH2_$_O``PO+?_\+RW_XDZZ`0103R\M_^).NA(D6$\O+?_\3KH.5
  896. MO%A/8`#_"E-U;@!-;VX`5'5E`%=E9`!4:'4`1G)I`%-A=``M+2T`2F%N`$9E_
  897. M8@!-87(`07!R`$UA>0!*=6X`2G5L`$%U9P!397``3V-T`$YO=@!$96,`9&-R'
  898. M;VXZ("5S("4R;&0@)7,@)3`R;&0Z)3`R;&0@("```$Y5__Y(YP`P)&T`""9MA
  899. M``Q";?_^#!(`,&T&#!(`.6\,+PI(>@!`3KK^5E!/#!(`,&TD#!(`.6X>,"W_?
  900. M_L'\``H2$DB!2,'0@9"\````,#M`__Y2BF#6-JW__B`*3-\,`$Y=3G5.;W0@2
  901. M82!N=6UB97(Z("5S"@!.50``+RT`#$ZZ`4983R\`+RT`#"\M``A.NA'P3^\`)
  902. M#$Y=3G5.50``+P1*K0`(9A!";(=\0FR'>G``*!].74YU4VT`$G``.`!(P&!8$
  903. M,"R'>K!LAWQF+DAX!`!(;(-Z+RT`"$ZZ$7)/[P`,.4"'?$)LAWI*;(=\;@P@\
  904. M;0`,0C!``'``8+8P+(=Z4FR'>D'L@WHB;0`,$[```$``##``"@``9PA21+AM%
  905. M`!)MHB!M``Q",$``<`%@A'(`8`02+P`/(&\`!"`O``C1P+"\````*&,$8`@16
  906. M`5'(__Q.=4CG/SXD"`@"``!G!!$!4X`"@0```/\D`>%"@D(T`4A"A($F`B@"N
  907. M*@(L`BX"(D(D0B9"*$(J0BQ"<C"0@6L(2.`_?I"!:OC003(\``2006L&(0*0V
  908. M06KZT$%@`A$"4<C__$S??/Q.=2!O``0@"")O``@0V6;\3G4@;P`$(`A*&&;\&
  909. MD<`@"%.`3G5A<$/L@O)%[(+RM<EF#C(\`5YK"'0`(L)1R?_\*4^('"QX``0IT
  910. M3H@@2.>`@`@N``0!*6<02_H`"$ZN_^)@!D*G\U].<T/Z`"!.KOYH*4"()&8,2
  911. M+CP``X`'3J[_E&`$3KH`&E!/3G5D;W,N;&EB<F%R>0!)^0``?_Y.=4Y5```OH
  912. M"DAY``$``#`L@NC!_``&+P!.NA%B*4"(*%!/9A1"ITAY``$``$ZZ$"!03RYL-
  913. MB!Q.=2!LB"A":``$(&R(*#%\``$`$"!LB"@Q?``!``H@;(@<("R(')"H``10K
  914. M@"E`B"P@;(@L(+Q-04Y80J=.NA$:)$!*J@"L6$]G,"\M``PO+0`(+PI.N@"R*
  915. M*7P````!B!@@;(@H`&B````$(&R(*`!H@```"D_O``Q@0DAJ`%Q.NA&:2&H`[
  916. M7$ZZ$00I0(@P(&R(,$JH`"103V<0(&R(,")H`"0O$4ZZ#H183R\LB#`O"DZZ1
  917. M`G@I;(@PB#103TZZ#KP@;(@H((!.N@[V(&R(*"%```9G%DAX`^U(>@`L3KH.P
  918. MSB!LB"@A0``,4$\O+(@T+RR(.$ZZ[.Y"ITZZ#')/[P`,)%].74YU*@!.50``A
  919. M2.<,,"1M`!`@;0`(2J@`K&<8(&T`""`H`*SE@"@`($0@*``0Y8`F0&`$)FR"3
  920. MZA`32(!(P-"M``Q4@"E`B#Q"IR\LB#Q.N@_T*4"(0%!/9@A,WPPP3EU.=1`3P
  921. M2(!(P"H`+P4@2U*(+P@O+(A`3KH!CB!LB$#1Q4/Z`5@0V6;\+RT`#"\*+RR(S
  922. M0$ZZ`4X@;(A`0C!8`"E\`````8@X(&R(0-'%)DA2BR1+3^\`&!`32(!(P"H`R
  923. ML+P````@9R"ZO`````EG&+J\````#&<0NKP````-9PBZO`````IF!%*+8,P,[
  924. M$P`@;0``C`P3`")F,E*+($M2BQ`02(!(P"H`9R`@2E**$(6ZO````")F$`P33
  925. M`")F!%*+8`9"*O__8`)@TF!$($M2BQ`02(!(P"H`9S"ZO````"!G*+J\````%
  926. M"6<@NKP````,9QBZO`````UG$+J\````"F<(($I2BA"%8,(@2E**0A!*A68"&
  927. M4XM2K(@X8`#_/$(20J<@+(@X4H#E@"\`3KH.N"E`B#103V8(0JR(.&``_KYZJ
  928. M`"9LB$!@'B`%Y8`@;(@T(8L(`"!+(`A*&&;\D<!3B%*(U\A2A;JLB#AMW"`%%
  929. MY8`@;(@T0K`(`&``_H(@`#`\?_]@!#`O``X@;P`$2AAF_%-((F\`"%-`$-E7`
  930. MR/_\9P)"$"`O``1.=4SO`P``!"`((B\`#&`"$-E7R?_\9P9206`"0AA1R?_\2
  931. M3G5.50``2.<.,"1M``A"ITAZ`(Y.N@Z"*4"(1%!/9@A,WPQP3EU.=2!M``PB/
  932. M:``D+RD`!$ZZ#N(H`%A/9U)(>@!M($0O*``V3KH.M"9`2H!03V<T2'@#[2\+F
  933. M3KH,*"P`4$]G)"`&Y8`J`"!%)6@`"`"D)48`G$AX`^U(>@`X3KH,!"5``*!0#
  934. M3R\$3KH.@%A/+RR(1$ZZ#(I"K(A$6$]@@&EC;VXN;&EB<F%R>0!724Y$3U<`I
  935. M*@!.50``+P0I;0`(AWY(;0`0+RT`#$AZ`!I.N@"\*``@;(=^0A`@!$_O``PH1
  936. M'TY=3G5.50``(&R'?E*LAWX0+0`+$(!(@$C`P+P```#_3EU.=4Y5``!(YP@@/
  937. M)&T`$`RM````!``49@@@;0`(*!!@%$JM``QO""!M``@H$&`&(&T`""@00JT`*
  938. M%$JM``QL$D2M``Q*A&P*1(0K?`````$`%"(M``P@!$ZZ`]9![("<4XH4L`@`L
  939. M(BT`#"`$3KH#SB@`9MY*K0`49P93BA2\`"T@"DS?!!!.74YU3E7_%$CG"#`DN
  940. M;0`()FT`#$*M__@K;0`0__P@2U*+$!!(@$C`*`!G``,TN+P````E9@`##D(M:
  941. M_R(K?`````'_]"M\````(/_P*WP``"<0_^P@2U*+$!!(@$C`*`"PO````"UF:
  942. M$$*M__0@2U*+$!!(@$C`*`"XO````#!F%"M\````,/_P($M2BQ`02(!(P"@`6
  943. MN+P````J9AH@;?_\6*W__"M0_^@@2U*+$!!(@$C`*`!@-$*M_^A@(G(*("W_<
  944. MZ$ZZ"8+0A)"\````,"M`_^@@2U*+$!!(@$C`*`!![("O"#```D@`9M*XO```'
  945. M`"YF8B!+4HL0$$B`2,`H`+"\````*F8:(&W__%BM__PK4/_L($M2BQ`02(!(2
  946. MP"@`8#1"K?_L8")R"B`M_^Q.N@D8T(20O````#`K0/_L($M2BQ`02(!(P"@`[
  947. M0>R`KP@P``)(`&;2*WP````$_^2XO````&QF%B!+4HL0$$B`2,`H`"M\````'
  948. M!/_D8!2XO````&AF#"!+4HL0$$B`2,`H`"`$8```@BM\````"/_@8!PK?````
  949. M``K_X&`2*WP````0_^!@""M\____]O_@+RW_Y$AM_R(O+?_@+RW__$ZZ_;(K;
  950. M0/_<("W_Y-&M__Q/[P`08%P@;?_\6*W__")0*TG_W"`)2AEF_)/`4XDK2?_D+
  951. M8$H@;?_\6*W__"@00>W_(2M(_]P0A&`HD+P```!C9^)3@&>2D+P````+9P#_C
  952. M;%F`9[)5@&<`_VQ7@&<`_W!@S$'M_R*1[?_<*TC_Y"`M_^2PK?_L;P8K;?_L,
  953. M_^1*K?_T9W`@;?_<#!``+6<*(&W_W`P0`"MF-`RM````,/_P9BI3K?_H(&W_J
  954. MW%*M_]P0$$B`2,`O`$Z2L+S_____6$]F"G#_3-\,$$Y=3G5@&"\M__!.DK"\G
  955. M_____UA/9@1P_V#B4JW_^"`M_^A3K?_HL*W_Y&[:0JW_X&`D(&W_W%*M_]P0[
  956. M$$B`2,`O`$Z2L+S_____6$]F!'#_8*I2K?_@(&W_W$H09PH@+?_@L*W_[&W*=
  957. M("W_X-&M__A*K?_T9BI@&DAX`"!.DK"\_____UA/9@9P_V``_W!2K?_X("W_0
  958. MZ%.M_^BPK?_D;MA@&"\$3I*PO/____]83V8&</]@`/](4JW_^&``_,`@+?_X'
  959. M8`#_.$CG2`!"A$J`:@1$@%)$2H%J!D2!"D0``6$^2D1G`D2`3-\`$DJ`3G5(A
  960. MYT@`0H1*@&H$1(!21$J!:@)$@6$:(`%@V"\!81(@`2(?2H!.=2\!808B'TJ`+
  961. M3G5(YS``2$%*068@2$$V`30`0D!(0(##(@!(0#("@L,P`4)!2$%,WP`,3G5(?
  962. M028!(@!"04A!2$!"0'0/T(#3@;:!8@22@U)`4<K_\DS?``Q.=4Y5```O"B1M^
  963. M``P@4K'J``1E&B`M``C`O````/\O`"\*3KH`SE!/)%].74YU(%)2DA`M``L0H
  964. M@$B`2,#`O````/]@Y$Y5```O"D'L@3`D2"!*U?P````6+PAA$%A/0>R"Z+7(I
  965. M9>HD7TY=3G5.50``2.<(("1M``AX`"`*9@IP_TS?!!!.74YU2BH`#&=2""H`L
  966. M`@`,9PQ(>/__+PIA5"@`4$\0*@`-2(!(P"\`3KH%-(B`""H``0`,6$]G"B\JS
  967. M``A.N@(\6$\(*@`%``QG$B\J`!).N@+8+RH`$DZZ`B)03T*20JH`!$*J``A"N
  968. M*@`,(`1@CDY5__Y(YP@@)&T`"$'Z_T0I2(A(""H`!``,9PIP_TS?!!!.74YUF
  969. M""H``@`,9S0@4I'J``@H""\$+RH`"!`J``U(@$C`+P!.N@*6L(1/[P`,9Q`(W
  970. MZ@`$``Q"DD*J``1P_V"\#*W_____``QF$`BJ``(`#$*20JH`!'``8*)*J@`(R
  971. M9@@O"DZZ`*183PQJ``$`$&8P&VT`#___2'@``4AM__\0*@`-2(!(P"\`3KH"P
  972. M,K"\`````4_O``QFF"`M``Q@`/]>)*H`"#`J`!!(P-"J``@E0``$".H``@`,P
  973. M(%)2DA`M``\0@$B`2,#`O````/]@`/\N3E4``"\*0>R!,"1(2BH`#&<8U?P``
  974. M```60>R"Z+7(90AP`"1?3EU.=6#B0I)"J@`$0JH`""`*8.I.5?_\+PHD;0`(6
  975. M2'@$`$ZZ`,(K0/_\6$]F\``$`$"!*T?P````.)4@`""1?3EU.=35\!```[
  976. M$`CJ``$`#"5M__P`"!`J``U(@$C`+P!.N@#>2H!83V<&`"H`@``,8,Q.50``S
  977. M2.<`,"1LAX)@%"92("H`!%"`+P`O"DZZ!AI03R1+(`IFZ$*LAX),WPP`3EU._
  978. M=4Y5```O"D'Z_\8I2(A,0J<@+0`(4(`O`$ZZ!<`D0$J`4$]F"'``)%].74YU\
  979. M)*R'@B5M``@`!"E*AX(@"E"`8.9.50``+RT`"&&V6$].74YU3E4``$CG`#"7_
  980. MRR1LAX)@#B!M``A1B+'*9Q(F2B12(`IF[G#_3-\,`$Y=3G4@"V<$)I)@!"E29
  981. MAX(@*@`$4(`O`"\*3KH%<'``4$]@V$Y5```O"G(&("T`"$ZZ`N`D0-7LB"A*%
  982. MK0`(;1(P+(+H2,`B+0`(LH!L!$J29A`I?`````*(4'#_)%].74YU<@8@+0`(Y
  983. M3KH"J"!LB"@O,`@`3KH#,$J`6$]G!'`!8`)P`?E4``"\M``A.N@+.2H!8F
  984. M3V8.3KH#!"E`B%!P_TY=3G5P`Ϥ``$CG#"`H+0`(3KH`=G(&(`1.N@)2)
  985. M)$#5[(@H2H1M#C`L@NA(P+B`;`1*DF82*7P````"B%!P_TS?!#!.74YU,"H`]
  986. M!,!\``-F#"E\````!8A0</]@XB\M`!`O+0`,+Q).N@,,*@"PO/____]/[P`,P
  987. M9@Q.N@)^*4"(4'#_8+H@!6"V3E7__$AX$`!"ITZZ!-8K0/_\"```#%!/9Q)*4
  988. MK(@89@@@+?_\3EU.=4ZZ``9P`&#T3E4``$AX``1(>@`<3KH";B\`3KH"J$AX@
  989. M``%.N@`.3^\`$$Y=3G5>0PH`3E4``$JLB$AG!B!LB$A.D"\M``A.N@`(6$].W
  990. M74YU3E7__"\$*VT`"/_\2JR(*&<L>`!@"B\$3KH`_%A/4H0P+(+H2,"X@&WLZ
  991. M,"R"Z,'\``8O`"\LB"A.N@.L4$]*K(A,9P8@;(A,3I!*K(+N9PHO+(+N3KH"Z
  992. M"EA/2JR(5&<((&R(5""LB%A*K(A<9PHO+(A<3KH"3EA/2JR(8&<*+RR(8$ZZI
  993. M`CY83TJLB&1G"B\LB&1.N@(N6$]*K(AH9PHO+(AH3KH"'EA/+'@`!`@N``0!T
  994. M*6<4+PU+^@`*3J[_XBI?8`9"I_-?3G-*K(@P9BI*K(A`9R(O+(@\+RR(0$ZZ&
  995. M`P@@+(@X4H#E@"\`+RR(-$ZZ`O9/[P`08`Y.N@+@+RR(,$ZZ`U!83R`M__PN%
  996. M;(@<3G4H'TY=3G5.50``2.<.("@M``AR!B`$3KH`1"1`U>R(*$J$;0XP+(+HQ
  997. M2,"X@&P$2I)F$BE\`````HA0</],WP1P3EU.=3`J``3`?(``9@@O$DZZ`#)8:
  998. M3T*2<`!@X$CG<``T`<3`)@%(0\;`2$-"0]2#2$#`P4A`0D#0@DS?``Y.=4[ZS
  999. M``(B+P`$+&R()$[N_]PB+P`$+&R()$[N_X(B+P`$+&R()$[N_T`B+P`$+&R(1
  1000. M)$[N_[@B+P`$+&R()$[N_U).^@`"3.\`!@`$+&R()$[N_YI,[P`.``0L;(@DV
  1001. M3N[_(BQLB"1.[O_*+&R()$[N_WPB+P`$+&R()$[N_RA.^@`"3.\`!@`$+&R(,
  1002. M)$[N_ZQ.^@`"3.\`!@`$+&R()$[N_^).^@`"+&R()$[N_\1.^@`"3.\`#@`$?
  1003. M+&R()$[N_]9.^@`"3.\`#@`$+&R()$[N_[Y.^@`"(B\`!"QLB"1.[O^F3OH`U
  1004. M`DSO``X`!"QLB"1.[O_0(F\`!"QLB"!.[OX@2.<!!$SO((``#"QLB"!.KO^4W
  1005. M3-\@@$YU(F\`!"QLB"!.[OXL(F\`!"QLB"!.[OX^3OH``B)O``0L;(@@3N[^O
  1006. M8DY5``!(YP@@2'C__TZZ`-`H`+"\_____UA/9@IP`$S?!!!.74YU2'D``0`!Y
  1007. M2'@`(DZZ`+@D0$J`4$]F#"\$3KH`Z'``6$]@UB5M``@`"A5M``\`"15\``0`D
  1008. M"$(J``X51``/0J=.N@"6)4``$$JM``A83V<*+PI.N@!:6$]@"DAJ`!1.N@"\S
  1009. M6$\@"F"23E4``"\*)&T`"$JJ``IG""\*3KH`V%A/%7P`_P`()7S_____`!1P>
  1010. M`!`J``\O`$ZZ`&Q(>``B+PI.N@!.3^\`#"1?3EU.=2)O``0L;(@@3N[^GB`O4
  1011. M``0L;(@@3N[^MD[Z``),[P`#``0L;(@@3N[_.D[Z``(B;P`$+&R(($[N_MHL4
  1012. M;(@@3N[_?$[Z``(B;P`$("\`""QLB"!.[O\N("\`!"QLB"!.[OZP(&\`!"QL2
  1013. MB"!.[OZ,(&\`!""(6)!"J``$(4@`"$YU(&\`!$SO`@$`""(O`!`L;(@@3N[^G
  1014. M1"QLB"`B;P`$("\`"$[N_=@B;P`$+&R(($[N_I@B;P`$+&R(($[N_H8B;P`$2
  1015. M+&R(($[N_C),[P`#``0L;(@@3N[^SB`O``0L;(@@3N[^PB)O``0L;(@@3N[^4
  1016. M)B!O``0L;(@@3N[^@$SO`P``!"QLB$1.[O^@(&\`!"QLB$1.[O^F(&\`!"QLW
  1017. MB$1.[O^R``````/L`````0````$``!'H`````````_(```/J````O``!'QP?<
  1018. M'A\>'Q\>'QX?```````!``$````[`!<`'P`,``8````!````/```!:```)V`7
  1019. M```%H````O````,L```#1````V0```-Q```/*@``#RX```\R```/-@``#SH`M
  1020. M``\^```/0@``#T8```]*```/3@``#U(```]6```/6@``#UX```]B```/9@``K
  1021. M#VH```]N```/<@``#W8P,3(S-#4V-S@Y86)C9&5F````("`@("`@("`@,#`P.
  1022. M,#`@("`@("`@("`@("`@("`@(""00$!`0$!`0$!`0$!`0$!`#`P,#`P,#`P,<
  1023. M#$!`0$!`0$`)"0D)"0D!`0$!`0$!`0$!`0$!`0$!`0$!`4!`0$!`0`H*"@H*(
  1024. M"@("`@("`@("`@("`@("`@("`@("0$!`0"```````````````````0`````!4
  1025. M``````````````````````$!`````0`````````````````````!`@````$`'
  1026. M`````````````````````````````````````````````````````````````
  1027. M`````````````````````````````````````````````````````````````
  1028. M`````````````````````````````````````````````````````````````
  1029. M`````````````````````````````````````````````````````````````
  1030. M`````````````````````````````````````````````````````````````
  1031. M`````````````````````````````````````````````````````````````
  1032. M`````````````````````````````````````````````````````````````
  1033. M`````````````````````````````````````````````````````````````
  1034. M````````````````````````%``````````````#[`````4````!````-@``_
  1035. M`#H````^````0@```$8````4`````````$H```!.````4@```%8```!:````N
  1036. M7@```&(```!F````:@```&X```!R````=@```'H```!^````@@```(8```"*P
  1037. @````C@```)(```"6`````````_(```/K`````0```_)^-
  1038. ``
  1039. end
  1040. size 10292
  1041. SHAR_EOF
  1042. cat << \SHAR_EOF > null-handler.uu
  1043.  
  1044. begin 644 null-handler
  1045. M```#\P`````````#``````````(```#*````%@````$```/I````RD[Z`FY.8
  1046. M50``(&T`""\H`!`@;0`(+R@`#"\M``PO+0`(80A/[P`03EU.=4Y5__@@;0`(I
  1047. M(6T`$``,(&T`""%M`!0`$"!M``@K:``$__@@;0`(*U#__"!M``@B;0`,T_P`>
  1048. M``!<(4D`!"!M__PA;0`(``H@;?_\0I`@;?_\0J@`!"\M__PO+?_X3KH"@E!/7
  1049. M3EU.=4Y5__@@;0`(T?P```!<*TC__"\M__Q.N@)P6$\O+?_\3KH"2%A/*T#_F
  1050. M^"!M__@@*``*3EU.=4Y5_^A!^@&"*TC__"M\`````?_L0JW_Z$*G3KH""%A/]
  1051. M*4"`$B\L@!).NO^@6$\K0/_X(&W_^"`H`!SE@"M`__0@;?_T(FR`$M/\````R
  1052. M7"%)``@@;?_X+R@`$$AX__\O+(`2+RW_^$ZZ_OQ/[P`02JW_[&<``0@O+(`2&
  1053. M3KK_3EA/*T#_^"!M__@@*``(8```QE*M_^@@;?_X("@`%.6`*T#_\"!M__@B#
  1054. M;?_P(V@`"``D(&W_\$*H``0@;?_X+R@`$$AX__\O+(`2+RW_^$ZZ_I9/[P`00
  1055. M8```HB!M__@O*``00J<O+(`2+RW_^$ZZ_GA/[P`08```A"!M__@B;?_X(V@`;
  1056. M'``,+RR`$B\M__A.NOXP4$]@9%.M_^AF!$*M_^P@;?_X+R@`$$AX__\O+(`2"
  1057. M+RW_^$ZZ_C!/[P`08#Q(>`#10J<O+(`2+RW_^$ZZ_AA/[P`08"20O````%)GB
  1058. M`/]\6X!GE)"\```#EF<`_R93@&<`_R!3@&>>8,1@`/[T(&W_]$*H``A.74YU)
  1059. M5F5R(#`N,"`H8RD@1W5N;F%R($YO<F1M87)K(#$Y.#@``&%P0^R`#D7L@`ZU4
  1060. MR68.,CP`$FL(=``BPE')__PI3X`6+'@`!"E.@`Y(YX"`""X`!`$I9Q!+^@`(O
  1061. M3J[_XF`&0J?S7TYS0_H`($ZN_F@I0(`:9@PN/``#@`=.KO^48`1.NOWV4$]."
  1062. M=61O<RYL:6)R87)Y`$GY``!__DYU3OH``B)O``0L;(`.3N[^VD[Z``(@;P`$6
  1063. M+&R`#D[N_HQ,[P,```0L;(`.3N[^DD[Z``(@;P`$+&R`#D[N_H````/L````,
  1064. M`0````$```+D`````````_(```/J`````P`4`````````````````_(```/K$
  1065. (`````0```_+D:
  1066. ``
  1067. end
  1068. size 908
  1069. SHAR_EOF
  1070. #    End of shell archive
  1071. exit 0
  1072. -- 
  1073. Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
  1074. Have five nice days.
  1075.